package com.micro.service.auth.server.config.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;

import com.micro.service.auth.server.config.oauth2.CustomClientDetailsService;
import com.micro.service.auth.server.config.properties.SecurityProperties;
import com.micro.service.auth.server.utils.AsyncThreadPool;
import com.micro.service.common.base.HeaderCommonInfo;
import com.micro.service.common.dto.ResultDTO;
import com.micro.service.common.enums.LoginType;
import com.micro.service.common.utils.BCryptUtils;
import com.micro.service.common.utils.ImeiUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

/**
 * @author lvxiucai
 * @description 登录成功处理
 * @date 2019/7/19
 */
@Component
@Slf4j
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;
    @Autowired
    private CustomClientDetailsService clientDetailsService;

    @Autowired
    private AsyncThreadPool asyncThreadPool;

    @Resource(name = "redisNewSysTemplate")
    private StringRedisTemplate newStringTemplate;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        log.info("登录成功");
        String state = request.getHeader("state");
        //登录成功之后的处理
        if(LoginType.JSON.equals(securityProperties.getBrowserProperties().getLoginType()) && StringUtils.equalsIgnoreCase("xyz",state) ){
            ResultDTO<OAuth2AccessToken> resultDTO = new ResultDTO<>();

            String header = request.getHeader("Authorization");
            if (header == null || !header.toLowerCase().startsWith("basic ")) {
                throw new UnapprovedClientAuthenticationException("请求头中无client信息！");
            }
            String[] tokens = this.extractAndDecodeHeader(header, request);
            assert tokens.length == 2;

            String clientId = tokens[0];
            String clientSecret = tokens[1];
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            if(clientDetails == null){
                throw new UnapprovedClientAuthenticationException("clientId不存在:"+clientId);
            }else if(!BCryptUtils.match(clientSecret, clientDetails.getClientSecret())){
                throw new UnapprovedClientAuthenticationException("clientSecret不正确:"+clientSecret);
            }
            TokenRequest tokenRequest = new TokenRequest(Maps.newHashMap(),clientId,clientDetails.getScope(),"custom");

            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);
            OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(auth2Authentication);

            resultDTO.success(oAuth2AccessToken);

            String imei = ImeiUtils.getImeiFromHeaders(request);
            HeaderCommonInfo headerCommonInfo = getHeaderCommonInfo(request);
            //登录成功之后的处理
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            //开启request子线程共享
            RequestContextHolder.setRequestAttributes(servletRequestAttributes,true);
            asyncThreadPool.getExecutor().execute(()->{
                try {
                    afterSuccessHandle(imei,clientId,oAuth2AccessToken,headerCommonInfo);
                } catch (ParseException e) {
                    e.printStackTrace();
                    log.error("登录成功之后异步处理异常！",e);
                }
            });

            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(resultDTO));
        }else {
            super.onAuthenticationSuccess(request,response,authentication);
        }

    }

    /**
     * @author lvxiucai
     * @description 登录成功后的异步处理--可以保存登录历史等
     * @date 2020/4/15
     * @return
     **/
    private void afterSuccessHandle(String imei, String clientId, OAuth2AccessToken oAuth2AccessToken,HeaderCommonInfo headerCommonInfo) throws ParseException {
        String userId = oAuth2AccessToken.getAdditionalInformation().get("userId").toString();

    }

    /**
     * @author lvxiucai
     * @description 解析请求头并获取basic客户端密钥信息
     * @date 2019/9/29
     * @param header
     * @param request
     * @return
     **/
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
        byte[] base64Token = header.substring(6).getBytes("UTF-8");

        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var7) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }



    /**
     * @author lvxiucai
     * @description 从请求头中获取信息
     * @date 2019/9/23
     * @param
     * @return
     **/
    public HeaderCommonInfo getHeaderCommonInfo(HttpServletRequest request) throws UnsupportedEncodingException {
        HeaderCommonInfo headerInfo = new HeaderCommonInfo();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            switch (key){
                case "source_type":
                    if(value != null){
                        headerInfo.setSourceType(value);
                    };break;
                case "imei":
                    if(value != null){
                        headerInfo.setImei(value);
                    };break;
                case "longitude":
                    if(value != null){
                        headerInfo.setLongitude(value);
                    };break;
                case "latitude":
                    if(value != null){
                        headerInfo.setLatitude(value);
                    };break;
                case "province":
                    if(value != null){
                        if(value.startsWith("%")){
                            headerInfo.setProvince(URLDecoder.decode(value,"UTF-8"));
                        }else {
                            headerInfo.setProvince(value);
                        }
                    };break;
                case "city":
                    if(value != null){
                        if(value.startsWith("%")){
                            headerInfo.setCity(URLDecoder.decode(value,"UTF-8"));
                        }else {
                            headerInfo.setCity(value);
                        }
                    };break;
                case "community":
                    if(value != null){
                        headerInfo.setCommunity(value);
                    };break;
                case "street":
                    if(value != null){
                        if(value.startsWith("%")){
                            headerInfo.setStreet(URLDecoder.decode(value,"UTF-8"));
                        }else {
                            headerInfo.setStreet(value);
                        }
                    };break;
                case "streetNum":
                    if(value != null){
                        headerInfo.setStreetNum(value);
                    };break;
                case "version":
                    if(value != null){
                        headerInfo.setVersion(value);
                    };break;
                case "machineType":
                    if(value != null){
                        headerInfo.setMachineType(value);
                    };break;
                case "machineClassify":
                    if(value != null){
                        headerInfo.setMachineClassify(value);
                    };break;
                case "address":
                    if(value != null){
                        if(value.startsWith("%")){
                            headerInfo.setAddress(URLDecoder.decode(value,"UTF-8"));
                        }else {
                            headerInfo.setAddress(value);
                        }
                    }break;

                default:
            }
        }
        return headerInfo;
    }

}
